/* @flow */

export default class VNode {
  tag: string | void;//虚拟dom对应的标签名
  data: VNodeData | void; //虚拟dom对应的选项数据对象
  children: ?Array<VNode>; //子虚拟组件
  text: string | void; //如果是文本节点，则该属性用于存储文本的内容
  elm: Node | void; //Vue对应的dom元素
  ns: string | void; //ns应该与svg有关
  context: Component | void; // rendered in this component's scope 当前虚拟dom对象渲染的Vue实例上下文
  //在创建函数式组件的虚拟dom对象时，会为该虚拟dom设置该属性，createFunctionalComponent方法中
  functionalContext: Component | void; // only for functional component root nodes
  //@todo 什么时候会在vnode对象上直接设置key属性？
  //1）在components\transition.js中会添加
  key: string | number | void; //唯一的关键字，用于patch
  componentOptions: VNodeComponentOptions | void;//VNode对象如果对应的是一个自定义组件，componentOptions保存组件相关事件、props数据等
  //当前虚拟dom对象对应的Vue实例，如果当前虚拟dom对应着一个组件，则
  //会设置该属性。
  componentInstance: Component | void; // component instance
  //存在一个one组件，整个应用的模板字符串为:<div><one></one></div>，
  //则对于整个应用来说，div会生成一个虚拟dom对象，one会生成div的子虚拟dom。
  //而one这个虚拟dom会根据one组件的信息（其实就是根据one的选项对象创建的Vue子构造函数）
  //生成一个Vue实例，这个Vue实例本身也可能存在对应的虚拟dom对象A，这个A的parent
  //属性就是指向one这个虚拟dom对象。
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  //标识当前vnode是静态树的根节点
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  //标识当前vnode是否为注释
  isComment: boolean; // empty comment placeholder?
  //是否是通过cloneVNode方法克隆的VNode对象，只有在处理静态vnode时才会克隆，详情可查看render-static.js
  isCloned: boolean; // is a cloned node?
  //@todo 什么是v-once节点？与静态节点有什么区别？
  isOnce: boolean; // is a v-once node? 是否是v-once元素的VNode对象
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.functionalContext = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}

export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  node.text = text
  node.isComment = true
  return node
}

export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val))
}

// optimized shallow clone
// used for static nodes and slot nodes because they may be reused across
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
export function cloneVNode (vnode: VNode): VNode {
  const cloned = new VNode(
    vnode.tag,
    vnode.data,
    vnode.children,
    vnode.text,
    vnode.elm,
    vnode.context,
    vnode.componentOptions,
    vnode.asyncFactory
  )
  cloned.ns = vnode.ns
  cloned.isStatic = vnode.isStatic
  cloned.key = vnode.key
  cloned.isComment = vnode.isComment
  cloned.isCloned = true
  return cloned
}

export function cloneVNodes (vnodes: Array<VNode>): Array<VNode> {
  const len = vnodes.length
  const res = new Array(len)
  for (let i = 0; i < len; i++) {
    res[i] = cloneVNode(vnodes[i])
  }
  return res
}
